<template>
  <el-card>
    <template #header>
      <span>广州地铁三维可视化</span>
    </template>
    <div
      ref="threeBox"
      style="height: 700px; width: 100%; background: #232323"
    ></div>
    <div style="color: #999; font-size: 13px; margin-top: 8px">
      🚇 支持多线路、站点高亮与多车辆动画，便于后续业务联动/交互扩展。
    </div>
  </el-card>
  <el-dialog v-model="showInfo" title="列车信息" width="350px" append-to-body>
    <div v-if="selectedTrain">
      <div>线路：{{ selectedTrain.mesh.userData.lineName }}</div>
      <div>车辆编号：{{ selectedTrain.mesh.userData.trainId }}</div>
      <div>当前位置：{{ getNearestStation(selectedTrain) }}</div>
      <div>当前参数 t: {{ selectedTrain.t.toFixed(2) }}</div>
    </div>
  </el-dialog>
</template>

<script setup>
import { ref, onMounted, onBeforeUnmount } from "vue";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import metroData from "@/assets/guangzhou-metro.json"; // 推荐放 src/assets/ 下，也可用 fetch()

const threeBox = ref(null);

let renderer, scene, camera, controls;
let animateId = null;
let lines = []; // 所有地铁线轨迹 Mesh
let stations = []; // 所有站点球 Mesh
let trains = []; // 所有列车对象

// 坐标缩放和平移参数，视你的原始数据而定（可微调让场景居中）
const CENTER = { lng: 113.25, lat: 23.13 }; // 广州大致中心
const SCALE = 168; // 缩放倍数，控制场景“大小”

const hoveredTrain = ref(null); // 当前悬停车辆
const selectedTrain = ref(null); // 当前点击车辆
const showInfo = ref(false); // 弹窗显示控制

let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();

function onMouseMove(event) {
  const rect = renderer.domElement.getBoundingClientRect();
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  raycaster.setFromCamera(mouse, camera); // 2. 设置射线投射器

  // 检测所有列车 mesh
  const intersects = raycaster.intersectObjects(trains.map((t) => t.mesh));
  // 4. 处理悬停逻辑
  if (intersects.length > 0) {
    // 4.1 找到当前悬停的列车对象
    hoveredTrain.value = trains.find((t) => t.mesh === intersects[0].object);
    // 高亮
    hoveredTrain.value.mesh.material.emissive?.set("#00ffe0");
  } else {
    // 5. 处理悬停离开逻辑
    if (hoveredTrain.value) {
      hoveredTrain.value.mesh.material.emissive?.set("#000");
      hoveredTrain.value = null;
    }
  }
}

function onMouseClick(event) {
  // 获取渲染器canvas元素在视口中的位置和尺寸信息
  const rect = renderer.domElement.getBoundingClientRect(); //将鼠标的屏幕坐标转换为Three.js使用的归一化设备坐标（NDC）
  // X坐标转换：从[0, width]映射到[-1, 1]
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
  // Y坐标转换：从[0, height]映射到[1, -1]（注意Y轴在Three.js中是向上的）
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1;
  // 使用更新后的鼠标位置和相机设置射线发射器
  raycaster.setFromCamera(mouse, camera);
  // 检测射线与所有列车网格对象的交点（trains是列车对象数组）
  const intersects = raycaster.intersectObjects(trains.map((t) => t.mesh));
  // 如果检测到交点
  if (intersects.length > 0) {
    // 通过匹配网格对象找到对应的列车对象
    selectedTrain.value = trains.find((t) => t.mesh === intersects[0].object);
    showInfo.value = true;
  } else {
    showInfo.value = false;
    selectedTrain.value = null; // 如果没有交点，清除选中状态
  }
}

function getNearestStation(trainObj) {
  // 获取列车当前所在曲线对象
  const curve = trainObj.curve;
  // 根据列车在曲线上的位置参数t（0到1之间）获取当前位置
  const pos = curve.getPointAt(trainObj.t);
  // 获取该线路所有站点坐标
  // 将线路的地理坐标（经纬度）转换为三维空间坐标
  const coords = trainObj.feature.geometry.coordinates.map(([lng, lat]) =>
    lnglatToXYZ(lng, lat)
  );
  // 初始化最小距离和最近站点索引
  let minDist = Infinity,
    nearestIdx = -1;
  // 遍历所有站点坐标
  coords.forEach((pt, idx) => {
    // 计算当前位置到该站点的距离
    const dist = pos.distanceTo(pt);
    // 更新最小距离和索引
    if (dist < minDist) {
      minDist = dist;
      nearestIdx = idx;
    }
  });
  // 从列车地理特征中获取站点信息
  const stations = trainObj.feature.properties.stations;
  // 返回站点名称，若无则返回"未知"
  return stations && stations[nearestIdx] ? stations[nearestIdx].name : "未知";
}
function onResize() {
  const w = threeBox.value.clientWidth,
    h = 700; // 你容器高度
  camera.aspect = w / h;
  camera.updateProjectionMatrix();
  renderer.setSize(w, h);
}
onMounted(() => {
  window.addEventListener("resize", onResize);
  threeBox.value.addEventListener("mousemove", onMouseMove, false);
  threeBox.value.addEventListener("click", onMouseClick, false);
  // 1. Three.js 场景初始化
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0x232323);

  camera = new THREE.PerspectiveCamera(
    60,
    threeBox.value.clientWidth / 540,
    0.1,
    1000
  );
  camera.position.set(12, 12, 12);
  camera.lookAt(0, 0, 0);

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(threeBox.value.clientWidth, 540);
  threeBox.value.appendChild(renderer.domElement);

  // 灯光
  scene.add(new THREE.AmbientLight(0xffffff, 0.6));
  const dirLight = new THREE.DirectionalLight(0xffffff, 1.2);
  dirLight.position.set(15, 25, 13);
  scene.add(dirLight);

  // 坐标轴
  // scene.add(new THREE.AxesHelper(3));

  // 2. 加载轨道与站点
  parseMetroData(metroData);

  // 控制器
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;

  animate();
});

onBeforeUnmount(() => {
  cancelAnimationFrame(animateId);
  renderer?.dispose?.();
  scene = null;
  camera = null;
  window.removeEventListener("resize", onResize);
  threeBox.value?.removeEventListener("mousemove", onMouseMove);
  threeBox.value?.removeEventListener("click", onMouseClick);
});

function lnglatToXYZ(lng, lat, y = 0) {
  //使用缩放系数(SCALE)将经纬度差值转换为三维距离
  // 经纬度转 three.js 空间坐标
  return new THREE.Vector3(
    (lng - CENTER.lng) * SCALE,
    y,
    (lat - CENTER.lat) * SCALE
  );
}

// 解析并渲染所有线路与站点
function parseMetroData(data) {
  // 遍历每条地铁线路
  data.features.forEach((feature) => {
    const color = feature.properties.color || "#00bbff";
    const coords = feature.geometry.coordinates; // [[lng, lat], ...]
    const points = coords.map(([lng, lat]) => lnglatToXYZ(lng, lat));
    // Tube 曲线（控制点数量足够多才平滑）
    const curve = new THREE.CatmullRomCurve3(points);

    // TubeGeometry 绘轨道
    const tubeGeo = new THREE.TubeGeometry(
      curve,
      Math.max(120, points.length * 8), //动态计算分段数（至少120段，点数×8保证平滑度）
      0.14, // 轨道半径
      14, // 截面分段数,14边形
      false // 是否闭合
    );
    const tubeMat = new THREE.MeshStandardMaterial({
      color,
      metalness: 0.38,
      roughness: 0.42,
    });
    const tubeMesh = new THREE.Mesh(tubeGeo, tubeMat);
    scene.add(tubeMesh);
    lines.push(tubeMesh);

    // 绘制站点球
    if (feature.properties.stations) {
      feature.properties.stations.forEach((station, idx) => {
        // 防错：确保和曲线点数量匹配
        if (points[idx]) {
          const sphere = new THREE.Mesh(
            new THREE.SphereGeometry(0.24, 24, 24), //金色球体（0.24单位半径）
            new THREE.MeshStandardMaterial({
              color: "#ffd600",
              emissive: "#fff46c", // 自发光颜色
              emissiveIntensity: 0.3, // 发光强度
            })
          );
          sphere.position.copy(points[idx]); // 设置位置
          sphere.userData = {
            // 附加元数据
            name: station.name,
            order: station.order,
            line: feature.properties.line_name,
          };
          scene.add(sphere);
          stations.push(sphere); // 保存引用
        }
      });
    }

    // 每条线可安排多辆车（模拟首末班、反向等）
    for (let i = 0; i < 2; i++) {
      const train = new THREE.Mesh(
        new THREE.BoxGeometry(0.6, 0.3, 0.28),
        new THREE.MeshStandardMaterial({ color: "#ea3366", metalness: 0.5 })
      );
      train.userData = {
        lineName: feature.properties.line_name,
        color: feature.properties.color,
        trainId: `${feature.properties.line_name}-${i + 1}`,
        // 可加方向、首末站等
      };
      scene.add(train);
      trains.push({
        // 保存列车对象
        mesh: train, // 网格引用
        curve, // 所属轨道曲线
        t: i * 0.4, // 每辆车初始位置不同
        speed: Math.random() * 0.0005, // 可微调速度，真实可用调度API
        feature, // 保存线路 feature
      });
    }
  });
}

function followTrain(train, ease = 0.08) {
  const tgt = train.curve.getPointAt(train.t);
  const back = train.curve.getTangentAt(train.t).clone().multiplyScalar(-3.5);
  const desired = tgt.clone().add(back).add(new THREE.Vector3(0, 1.5, 0));
  camera.position.lerp(desired, ease);
  controls.target.lerp(tgt, ease);
}

// 动画主循环
function animate() {
  animateId = requestAnimationFrame(animate);

  // 列车动画
  trains.forEach((obj) => {
    obj.t += obj.speed;
    if (obj.t > 1) obj.t = 0;
    const pos = obj.curve.getPointAt(obj.t);
    const tangent = obj.curve.getTangentAt(obj.t);
    obj.mesh.position.copy(pos);
    // 小车朝向
    obj.mesh.quaternion.setFromUnitVectors(new THREE.Vector3(1, 0, 0), tangent);
  });
  if (selectedTrain.value) followTrain(selectedTrain.value);
  controls.update();

  renderer.render(scene, camera);
}
</script>
